// 
//  Copyright © 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as
//  published by the Free Software Foundation, either version 3 of the
//  License, or (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
// 
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// 


using System;
using System.Text;
using System.Collections.Generic;

using System.Security.Cryptography;

using Galaxium.Protocol.Xmpp.Library.Core;
using Galaxium.Protocol.Xmpp.Library.Xml;
using Galaxium.Protocol.Xmpp.Library.Utility;

using Anculus.Core;

namespace Galaxium.Protocol.Xmpp.Library.Extensions.Disco
{
	public class Info
	{
		private List<Identity> _all_identities =
			new List<Identity> ();
		private Dictionary<string, Identity> _identities = 
			new Dictionary<string, Identity> ();
		private List<string> _features =
			new List<string> ();
		private List<DataForm> _forms =
			new List<DataForm> ();
		
		private bool _sorted;
		
		public Info ()
		{
		}
		
		public Info (Element query)
		{
			foreach (Element elm in query) {
				switch (elm.Name) {
					case "identity":
						AddIdentity (new Identity (elm ["category"], elm ["type"],
						                           elm ["name"], elm ["xml:lang"]));
						break;
					case "feature":
						AddFeature (elm ["var"]);
						break;
					case "x":
						if (elm.Namespace == Namespaces.DataForms) {
							var form = new DataForm (elm, null);
							if (!String.IsNullOrEmpty (form.Type))
								AddForm (form);
						}
						break;
				}
			}
			
			EnsureSorted ();
		}
		
		private void EnsureSorted ()
		{
			if (_sorted) return;
			_all_identities.Sort ();
			_features.Sort (OctetCollationComparer.Instance);
			_forms.Sort ((a, b) => OctetCollationComparer.Instance.Compare (a.Type, b.Type));
			_sorted = true;
		}
		
		public IEnumerable<Identity> Identities {
			get { return _identities.Values; }
		}
		
		public IEnumerable<string> Features {
			get {
				EnsureSorted ();
				return _features;
			}
		}
		
		public IEnumerable<DataForm> Forms {
			get {
				EnsureSorted ();
				return _forms;
			}
		}
		
		public void AddIdentity (Identity identity)
		{		
			_all_identities.Add (identity);
			
			var path = identity.Category + '/' + identity.Type;
			if (_identities.ContainsKey (path)) {
				var stored_identity = _identities [path];
				var lang = System.Globalization.CultureInfo.CurrentCulture.IetfLanguageTag;
				
				if (stored_identity.Language == lang) return;
				if (identity.Language == lang) {
					_identities [path] = identity;
					return;
				}
				
				if (stored_identity.Language == null) return;
				if (identity.Language == null) {
					_identities [path] = identity;
					return;
				}
				
				if (stored_identity.Language == "en") return;
			}
			
			_identities [path] = identity;
			
			_sorted = false;
		}
		
		public void AddFeature (string feature)
		{
			_features.Add (feature);
			_sorted = false;
		}
		
		public void RemoveFeature (string feature)
		{
			_features.Remove (feature);
			_sorted = false;
		}
		
		public void AddForm (DataForm form)
		{
			if (String.IsNullOrEmpty (form.Type))
				throw new ArgumentException ();
			_forms.Add (form);
			_sorted = false;
		}
		
		private int FindForm (string type)
		{
			EnsureSorted ();
			var reference = new DataForm (FormAction.Form, null);
			reference.Type = type;
			return _forms.BinarySearch (reference);
		}
		
		public void RemoveForm (string type)
		{
			_forms.RemoveAt (FindForm (type));
		}
				
		public DataForm GetForm (string type)
		{
			return _forms [FindForm (type)];
		}
		
		public bool Supports (string feature)
		{
			EnsureSorted ();
			return _features.BinarySearch (feature, OctetCollationComparer.Instance) >= 0;
		}
		
		public static Iq CreateRequest (JabberID jid, string node)
		{
			var iq = new Iq (IqType.Get, Namespaces.DiscoInfo);
			iq.To = jid;
			if (node != null)
				iq.Query ["node"] = node;
			return iq;
		}
		
		public Iq CreateResult (JabberID jid, string node, string id)
		{
			EnsureSorted ();
			
			var iq = new Iq (IqType.Result, Namespaces.DiscoInfo);
			iq.To = jid;
			iq.ID = id;
			iq.Query ["node"] = node;

			foreach (var identity in _all_identities) {
				var element = new Element ("identity");
				if (identity.Category != null)
					element ["category"] = identity.Category;
				if (identity.Type != null)
					element ["type"] = identity.Type;
				if (identity.Name != null)
					element ["name"] = identity.Name;
				if (identity.Language != null)
					element ["xml:lang"] = identity.Language;
				iq.Query.AppendChild (element);
			}
			
			foreach (var feature in _features) {
				var element = new Element ("feature");
				element ["var"] = feature;
				iq.Query.AppendChild (element);
			}
			
			foreach (var form in _forms) {
				iq.Query.AppendChild (form.ToXml (false));
			}
		
			return iq;
		}
		
		public string GenerateVerificationString ()
		{
			EnsureSorted ();
			
			var sb = new StringBuilder ();
			
			foreach (var identity in _all_identities) {
				sb.Append ((identity.Category ?? String.Empty) + "/");
				sb.Append ((identity.Type ?? String.Empty) + "/");
				sb.Append ((identity.Language ?? String.Empty) + "/");
				sb.Append (identity.Name + "<");
			}
			
			foreach (var feature in _features)
				sb.Append (feature + "<");
			
			foreach (var form in _forms)
				sb.Append (form.GenerateVerificationString ());
			
			var str = sb.ToString ();
			
			Log.Debug ("Verification string base: " + str);
			
			var sha1 = SHA1.Create ();
			var bytes = sha1.ComputeHash (Encoding.UTF8.GetBytes (str));
			return Convert.ToBase64String (bytes);
		}
	}
}
